/*
 * This file is part of aion-unique <aion-unique.org>.
 *
 *  aion-unique is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  aion-unique is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with aion-unique.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.aionengine.gameserver.services;

import com.aionengine.gameserver.configs.main.MessageCustom;
import com.aionengine.gameserver.model.Race;
import com.aionengine.gameserver.world.World;
import com.aionengine.gameserver.world.knownlist.Visitor;
import com.aionengine.gameserver.configs.main.PunishmentConfig;
import com.aionengine.gameserver.configs.main.CustomConfig;
import com.aionengine.gameserver.configs.main.GroupConfig;
import com.aionengine.gameserver.configs.main.LoggingConfig;
import com.aionengine.gameserver.controllers.attack.AggroInfo;
import com.aionengine.gameserver.controllers.attack.KillList;
import com.aionengine.gameserver.model.gameobjects.player.Player;
import com.aionengine.gameserver.model.gameobjects.player.RewardType;
import com.aionengine.gameserver.model.team2.alliance.PlayerAlliance;
import com.aionengine.gameserver.model.team2.group.PlayerGroup;
import com.aionengine.gameserver.network.aion.AionServerPacket;
import com.aionengine.gameserver.network.aion.serverpackets.SM_SYSTEM_MESSAGE;
import com.aionengine.gameserver.questEngine.QuestEngine;
import com.aionengine.gameserver.questEngine.model.QuestEnv;
import com.aionengine.gameserver.services.abyss.AbyssPointsService;
import com.aionengine.gameserver.services.item.ItemService;
import com.aionengine.gameserver.utils.MathUtil;
import com.aionengine.gameserver.utils.PacketSendUtility;
import com.aionengine.gameserver.utils.ThreadPoolManager;
import com.aionengine.gameserver.utils.audit.AuditLogger;
import com.aionengine.gameserver.utils.stats.AbyssRankEnum;
import com.aionengine.gameserver.utils.stats.StatFunctions;
import com.aionengine.gameserver.configs.main.PvPConfig;
import com.aionemu.commons.utils.Rnd;
import com.aionengine.gameserver.model.DescriptionId;
import com.aionengine.gameserver.services.PvPRewardService;
import com.aionengine.gameserver.services.FFAInstance;
import com.aionengine.gameserver.services.custom.FFAManager;
import com.aionengine.gameserver.services.custom.NewbiePVP;
import com.aionengine.gameserver.services.custom.PVPInstanceService;

import javolution.util.FastMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * @author Sarynth
 */
public class PvpService {

    private static Logger log = LoggerFactory.getLogger("KILL_LOG");

    public static final PvpService getInstance() {
        return SingletonHolder.instance;
    }

    private FastMap<Integer, KillList> pvpKillLists;

    private PvpService() {
        pvpKillLists = new FastMap<Integer, KillList>();
    }

    /**
     * @param winnerId
     * @param victimId
     * @return
     */
    private int getKillsFor(int winnerId, int victimId) {
        KillList winnerKillList = pvpKillLists.get(winnerId);

        if (winnerKillList == null)
            return 0;
        return winnerKillList.getKillsFor(victimId);
    }

    /**
     * @param winnerId
     * @param victimId
     */
    private void addKillFor(int winnerId, int victimId) {
        KillList winnerKillList = pvpKillLists.get(winnerId);
        if (winnerKillList == null) {
            winnerKillList = new KillList();
            pvpKillLists.put(winnerId, winnerKillList);
        }
        winnerKillList.addKillFor(victimId);
    }

    /**
     * @param victim
     */
    public void doReward(Player victim) {
        // winner is the player that receives the kill count
        final Player winner = victim.getAggroList().getMostPlayerDamage();

        int totalDamage = victim.getAggroList().getTotalDamage();

        if (totalDamage == 0 || winner == null) {
            return;
        }
        
        int ss = winner.getFFAkills();
        int sss = winner.getPVPkills();
        int adolf = winner.getPVP2kills();
        int dotakill = winner.getDotakills();
        
        if (FFAManager.isInFFA(winner)){
        	//victim.setFFAkills(0);
            {
            //Iterator<Player> iter = winner.getPosition().getWorldMapInstance().playerIterator();
            Iterator<Player> iter = World.getInstance().getPlayersIterator();
            
            while (iter.hasNext()) {
			 if(ss == 5){
				 PacketSendUtility.sendPacket(iter.next(), new SM_SYSTEM_MESSAGE(1501157,winner.getName()));
			 
			 } else if (ss == 15) {
				 
				 PacketSendUtility.sendPacket(iter.next(), new SM_SYSTEM_MESSAGE(1501158,winner.getName()));
			 
			 } else if (ss == 25) {
				 
				 PacketSendUtility.sendPacket(iter.next(), new SM_SYSTEM_MESSAGE(1501159,winner.getName()));
			 
			 } else {
			
				//PacketSendUtility.sendMessage(iter.next(), "\uE022[FFA]: Guerrero " + winner.getName() + " comete una serie de " +ss+ " asesinatos, dando matanza a " +victim.getName());
				 PacketSendUtility.sendPacket(iter.next(), new SM_SYSTEM_MESSAGE(1501155,victim.getName(),winner.getName()));
			 }
			}
                
            }
            }
       if(PVPInstanceService.isInPVP(winner)){
           {
               
            Iterator<Player> iter = World.getInstance().getPlayersIterator();

		while (iter.hasNext()) {
			PacketSendUtility.sendMessage(iter.next(), "[PvP]: Jugador " + winner.getName() + " comete una serie de " +sss+ " asesinatos, dando matanza a " +victim.getName());
		}
                
            }
       }

       if(NewbiePVP.isInPVP(winner)){
           { 
        	   Iterator<Player> iter = World.getInstance().getPlayersIterator();

		while (iter.hasNext()) {
			PacketSendUtility.sendMessage(iter.next(), "[NewPvP]: Jugador " + winner.getName() + " comete una serie de " +adolf+ " asesinatos, dando matanza a " +victim.getName());
			}
                
           }
       }
       if(FFAInstance.isInBG(winner)){
           {
               
        	   Iterator<Player> iter = winner.getPosition().getWorldMapInstance().playerIterator();

		while (iter.hasNext()) {
			//PacketSendUtility.sendMessage(iter.next(), "\uE022[BG]: Guerrero " + winner.getName() + " comete una serie de " +adolf+ " asesinatos, dando matanza a " +victim.getName());
			PacketSendUtility.sendPacket(iter.next(), new SM_SYSTEM_MESSAGE(1501156,winner.getName(),adolf,victim.getName()));
			}
                
           }
       }

        // Add Player Kill to record.
        if (this.getKillsFor(winner.getObjectId(), victim.getObjectId()) < CustomConfig.MAX_DAILY_PVP_KILLS) {
			winner.getAbyssRank().setAllKill();
            int kills = winner.getAbyssRank().getAllKill();
            FFAManager.onKill(winner, victim);
            NewbiePVP.onKill(winner, victim);
            PVPInstanceService.onKill(winner, victim);
            FFAInstance.onKill(winner, victim);
            // Pvp Kill Reward.
            if (CustomConfig.ENABLE_KILL_REWARD) {
                if (kills % CustomConfig.KILLS_NEEDED1 == 1) {
                	switch ((int)Rnd.get(1, 3)) {
                		case 1:
                			ItemService.addItem(winner, CustomConfig.REWARD1, 1);
                            PacketSendUtility.sendMessage(winner, "Enhorabuena, has ganado " + "[item: " + CustomConfig.REWARD1 + "] por haber matado " + CustomConfig.KILLS_NEEDED1 + " jugadores!");
                            log.info("[RECOMPENSA:1] Jugador [" + winner.getName() + "] gano [" + CustomConfig.REWARD1 + "]");
                			break;
                		case 2:
                			PacketSendUtility.sendMessage(winner, "Lo sentimos, Muerte: " + CustomConfig.KILLS_NEEDED1 + " suerte en la siguiente.");
                			log.info("[FAIL:2] Jugador [" + winner.getName() + "] que mala suerte!");
                			break;
                		case 3:
                			PacketSendUtility.sendMessage(winner, "Lo sentimos, Muerte: " + CustomConfig.KILLS_NEEDED1 + " suerte en la siguiente.");
                			log.info("[FAIL:3] Jugador [" + winner.getName() + "] que mala suerte!");
                			break;
                	}
                }
                if (kills % CustomConfig.KILLS_NEEDED2 == 3) {
                    ItemService.addItem(winner, CustomConfig.REWARD2, 1);
                    PacketSendUtility.sendMessage(winner, "Congratulations, you have won " + "[item: " + CustomConfig.REWARD2
                            + "] for having killed " + CustomConfig.KILLS_NEEDED2 + " players !");
                    log.info("[REWARD] Player [" + winner.getName() + "] win 4 [" + CustomConfig.REWARD2 + "]");
                }
                if (kills % CustomConfig.KILLS_NEEDED3 == 5) {
                    ItemService.addItem(winner, CustomConfig.REWARD3, 1);
                    PacketSendUtility.sendMessage(winner, "Congratulations, you have won " + "[item: " + CustomConfig.REWARD3
                            + "] for having killed " + CustomConfig.KILLS_NEEDED3 + " players !");
                    log.info("[REWARD] Player [" + winner.getName() + "] win 6 [" + CustomConfig.REWARD3 + "]");
                }
            }
        }
        
        //Evitamos que los kill cuenten en los eventos de PvP
        if (FFAManager.isInFFA(winner) || FFAInstance.isInBG(winner)){

		} else {
			winner.increaseDotakills();
		}
        
        //Reiniciamos el contador a 0
        final String loc = MuiService.getInstance().getMessage("LOCATE_STRING01",victim.getWorldId(),victim.getX(),victim.getY());
        victim.setDotakills(0);

        if (!CustomConfig.CHANNEL_WORLD_KILL) {
            // Announce that player has died. send for visible object
            PacketSendUtility.broadcastPacketAndReceive(victim, SM_SYSTEM_MESSAGE.STR_MSG_COMBAT_FRIENDLY_DEATH_TO_B(victim.getName(), winner.getName()));
            } else {
            
            if (FFAManager.isInFFA(victim) || FFAInstance.isInBG(victim)){ //Evitamos que salga un mensaje dentro del evento
            } else {
            // Announce that player has died.
            PacketSendUtility.broadcastPacketAndReceive(victim, SM_SYSTEM_MESSAGE.STR_MSG_COMBAT_FRIENDLY_DEATH_TO_B(victim.getName(), winner.getName()));
            }
            // Announce that player has died. send for all online player's.
   			
            //Announce Kill de PvP con Efectos, el contador inicia desde 0
            // 0 = 1 Kill
            // 1 = 2 kill
            // 2 = 3 kill
            if(dotakill == 0) {
            	PVPKillAnnounce(victim);
            	ThreadPoolManager.getInstance().schedule(new Runnable() {
   					@Override
   					public void run() {
   						winner.setDotakills(0);					
   					} 
   				}, CustomConfig.REWARD_KILLERSYSTEM_SECONDS * 1000);
            } else if(dotakill == 1){ //Double Kill
            	sendPacketMESSAGE(new SM_SYSTEM_MESSAGE(1501175,winner.getName(),victim.getName(),loc)); //STR_CHAT_IDAsteria_IU_NPC_Gossip_19				
            } else if(dotakill == 2){
   				sendPacketMESSAGE(new SM_SYSTEM_MESSAGE(1501161,winner.getName(),victim.getName())); //STR_CHAT_IDAsteria_IU_Nmd_Gossip_01   				
   			 } else if (dotakill == 3){ 
   				sendPacketMESSAGE(new SM_SYSTEM_MESSAGE(1501162,winner.getName(),victim.getName())); //STR_CHAT_IDAsteria_IU_Nmd_Gossip_02
   			 } else if (dotakill == 4){ 
    			sendPacketMESSAGE(new SM_SYSTEM_MESSAGE(1501163,winner.getName(),victim.getName())); //STR_CHAT_IDAsteria_IU_Nmd_Gossip_03
    		 } else if (dotakill == 5){ 
    			 sendPacketMESSAGE(new SM_SYSTEM_MESSAGE(1501164,winner.getName(),victim.getName())); //STR_CHAT_IDAsteria_IU_Nmd_Gossip_04
    		 } else if (dotakill == 6){ 
    			 sendPacketMESSAGE(new SM_SYSTEM_MESSAGE(1501165,winner.getName(),victim.getName())); //STR_CHAT_IDAsteria_IU_Nmd_Gossip_05 //Ultra Kill
    		 } else if (dotakill == 7){ 
    			 sendPacketMESSAGE(new SM_SYSTEM_MESSAGE(1501166,winner.getName(),victim.getName())); //STR_CHAT_IDAsteria_IU_Nmd_Gossip_06
    		 } else if (dotakill == 8){ 
    	   		 sendPacketMESSAGE(new SM_SYSTEM_MESSAGE(1501167,winner.getName(),victim.getName())); //STR_CHAT_IDAsteria_IU_NPC_Gossip_12
       		 } else if (dotakill == 9){ //Kill 10
       			 sendPacketMESSAGE(new SM_SYSTEM_MESSAGE(1501168,winner.getName(),victim.getName())); //STR_CHAT_IDAsteria_IU_NPC_Gossip_14 //Combo
       			 ItemService.addItem(winner, CustomConfig.REWARD_KILLERSYSTEM, CustomConfig.REWARD_KILLERSYSTEM_COUNT); //186000242
       			log.info("[KILL-SYSTEM] Player [" + winner.getName() + "] ha hecho su kill " + dotakill + " matando a [" + victim.getName() + "]");
       		 } else {
   				PVPKillAnnounce(victim);
   			 }
           }

        //Kill-log
        if (LoggingConfig.LOG_KILL) {
        	if (dotakill == 9) { // Evitamos que guarde un log doble al llegar a 10 kill
        		
        	} else {
        		log.info("[KILL] Player [" + winner.getName() + "] killed [" + victim.getName() + "]");
        	}
        }
        
        //Guardamos muertes hechas en player vs player
        int deathPlayer = victim.getCommonData().getDeathPlayer();
        deathPlayer++;
        victim.getCommonData().setDeathPlayer(deathPlayer);
        
        int reduceap = PunishmentConfig.PUNISHMENT_REDUCEAP;
      	if (reduceap < 0)
      			reduceap *= -1;
      	if (reduceap > 100) {
      			reduceap = 100;
      	}

    	if ((LoggingConfig.LOG_PL) || (reduceap > 0)) {
    			 String ip1 = winner.getClientConnection().getIP();
    			 String mac1 = winner.getClientConnection().getMacAddress();
    			 String ip2 = victim.getClientConnection().getIP();
    			 String mac2 = victim.getClientConnection().getMacAddress();
    			 if (mac1 != null && mac2 != null) {
    				if (ip1.equalsIgnoreCase(ip2) && mac1.equalsIgnoreCase(mac2)) {
    				   AuditLogger.info(winner, "[PL] Possible Power Leveling : " + winner.getName() + " with " + victim.getName() + "; same ip=" + ip1 + " and mac=" + mac1 + ".");
    				   if (reduceap > 0) {
    							int win_ap = winner.getAbyssRank().getAp() * reduceap / 100;
    							int vic_ap = victim.getAbyssRank().getAp() * reduceap / 100;
    							AbyssPointsService.addAp(winner, -win_ap);
    							AbyssPointsService.addAp(victim, -vic_ap);
    							PacketSendUtility.sendMessage(winner, "[PL-AP] Perdiste " + reduceap + "% del total de abismo");
    							PacketSendUtility.sendMessage(victim, "[PL-AP] Perdiste " + reduceap + "% del total de abismo");
    	       }
    				}
    				else if (mac1.equalsIgnoreCase(mac2)) {
    				   AuditLogger.info(winner, "[PL] Possible Power Leveling : " + winner.getName() + " with " + victim.getName() + "; same mac=" + mac1 + ".");
    				}
    			 }
    	}
    	
    	// Check PVP Reward is Enabled
    	if (PvPConfig.PVPREWARD_ENABLE) {
    			int kills = (winner.getAbyssRank().getAllKill() + 1);
    			if ((kills % PvPConfig.PVPREWARD_KILLS_NEEDED1) == 0) {
    				ItemService.addItem(winner, PvPConfig.PVPREWARD_ITEM_REWARD1, 1);
    				PacketSendUtility.sendMessage(winner, "Congratulations, you get a [item: " + PvPConfig.PVPREWARD_ITEM_REWARD1 + "] for " + PvPConfig.PVPREWARD_KILLS_NEEDED1 + " new pvp kills");
    				switch (winner.getRace()) {
    					case ASMODIANS:
    							ItemService.addItem(winner, PvPConfig.PVPREWARD_ITEM_REWARD4, 2);
    							ItemService.addItem(winner, PvPConfig.PVPREWARD_ITEM_REWARD6, 2);
    						break;
    					case ELYOS:
    							ItemService.addItem(winner, PvPConfig.PVPREWARD_ITEM_REWARD5, 2);
    							ItemService.addItem(winner, PvPConfig.PVPREWARD_ITEM_REWARD7, 2);
    						break;
    				}
    			}
    		if ((kills % PvPConfig.PVPREWARD_KILLS_NEEDED2) == 0) {
    			ItemService.addItem(winner, PvPConfig.PVPREWARD_ITEM_REWARD2, 1);
    			PacketSendUtility.sendMessage(winner, "Congratulations, you get a [item: " + PvPConfig.PVPREWARD_ITEM_REWARD2 + "] for " + PvPConfig.PVPREWARD_KILLS_NEEDED2 + " new pvp kills");
    				switch (winner.getRace()) {
    					case ASMODIANS:
    							ItemService.addItem(winner, PvPConfig.PVPREWARD_ITEM_REWARD6, 1);
    						break;
    					case ELYOS:
    							ItemService.addItem(winner, PvPConfig.PVPREWARD_ITEM_REWARD7, 1);
    						break;
    				}
    			}
    			if ((kills % PvPConfig.PVPREWARD_KILLS_NEEDED3) == 0) {
    				ItemService.addItem(winner, PvPConfig.PVPREWARD_ITEM_REWARD3, 1);
    				PacketSendUtility.sendMessage(winner, "Congratulations, you get a [item: " + PvPConfig.PVPREWARD_ITEM_REWARD3 + "] for " + PvPConfig.PVPREWARD_KILLS_NEEDED3 + " new pvp kills");
    			}
    		}
    	
    	// Another PVP reward
    			if (winner.getLevel() - victim.getLevel() <= PvPConfig.MAX_AUTHORIZED_LEVEL_DIFF) {
    				if (getKillsFor(winner.getObjectId(), victim.getObjectId()) < PvPConfig.CHAIN_KILL_NUMBER_RESTRICTION) {
    					if (PvPConfig.ENABLE_MEDAL_REWARDING) {
    						if (Rnd.get() * 100 < PvPRewardService.getMedalRewardChance(winner, victim)) {
    							int medalId = PvPRewardService.getRewardId(winner, victim, false);
    							long medalCount = PvPRewardService.getRewardQuantity(winner, victim);
    							ItemService.addItem(winner, medalId, medalCount);
    							if (winner.getInventory().getItemCountByItemId(medalId) > 0) {
    								if (medalCount == 1)
    									PacketSendUtility.sendPacket(winner, new SM_SYSTEM_MESSAGE(1390000, new DescriptionId(medalId)));
    								else {
    									PacketSendUtility.sendPacket(winner, new SM_SYSTEM_MESSAGE(1390005, medalCount, new DescriptionId(medalId)));
    								}
    							}
    						}
    					}

    					if (PvPConfig.GENOCIDE_SPECIAL_REWARDING != 0) {
    						switch (PvPConfig.GENOCIDE_SPECIAL_REWARDING) {
    							case 1:
    								if (winner.getSpreeLevel() > 2 || Rnd.get() * 100 < PvPConfig.SPECIAL_REWARD_CHANCE) {
    									int abyssId = PvPRewardService.getRewardId(winner, victim, true);
    									ItemService.addItem(winner, abyssId, 1);
    									log.info("[PvP][Advanced] {Player : " + winner.getName() + "} has won " + abyssId + " for killing {Player : " + victim.getName() + "}");
    								}
    								break;
    							default:
    								if (winner.getSpreeLevel() > 2 || Rnd.get() * 100 < PvPConfig.SPECIAL_REWARD_CHANCE) {
    									ItemService.addItem(winner, PvPConfig.GENOCIDE_SPECIAL_REWARDING, 1L);
    									log.info("[PvP][Advanced] {Player : " + winner.getName() + "} has won " + PvPConfig.GENOCIDE_SPECIAL_REWARDING + " for killing {Player : " + victim.getName() + "}");
    								}
    								break;
    						}
    					}
    				}
    				else {
    					PacketSendUtility.sendMessage(winner, "You're not gaining anything from player " + victim.getName() + " today due to overkill.");
    				}
    			}

        // Keep track of how much damage was dealt by players
        // so we can remove AP based on player damage...
        int playerDamage = 0;
        boolean success;

        // Distribute AP to groups and players that had damage.
        for (AggroInfo aggro : victim.getAggroList().getFinalDamageList(true)) {
            success = false;
            if (aggro.getAttacker() instanceof Player) {
                success = rewardPlayer(victim, totalDamage, aggro);
            } else if (aggro.getAttacker() instanceof PlayerGroup) {
                success = rewardPlayerGroup(victim, totalDamage, aggro);
            } else if (aggro.getAttacker() instanceof PlayerAlliance) {
                success = rewardPlayerAlliance(victim, totalDamage, aggro);
            }

            // Add damage last, so we don't include damage from same race. (Duels, Arena)
            if (success)
                playerDamage += aggro.getDamage();
        }

        SerialKillerService.getInstance().updateRank(winner, victim);

        SerialKillerService.getInstance().onKillSerialKiller(winner, victim);

        //notify Quest engine for winner + his group
        notifyKillQuests(winner, victim);

        // Apply lost AP to defeated player
        //final int apLost = StatFunctions.calculatePvPApLost(victim, winner);
        //final int apActuallyLost = (int) (apLost * playerDamage / totalDamage);
        final int apLost = FFAManager.isInFFA(victim)?0:StatFunctions.calculatePvPApLost(victim, winner);
        final int apActuallyLost = FFAManager.isInFFA(victim)?0:(int) (apLost * playerDamage / totalDamage);

        if (apActuallyLost > 0)
            AbyssPointsService.addAp(victim, -apActuallyLost);

    }
    
    //Reiniciamos los Kill a 0 si el jugador vuelve a logear
    public void onLogout(Player player) {
    	player.setDotakills(0);
    }

    /**
     * @param victim
     * @param totalDamage
     * @param aggro
     * @return true if group is not same race
     */
    private boolean rewardPlayerGroup(Player victim, int totalDamage, AggroInfo aggro) {
        // Reward Group
        PlayerGroup group = ((PlayerGroup) aggro.getAttacker());

        // Don't Reward Player of Same Faction.
        if (group.getRace() == victim.getRace())
            return false;

        // Find group members in range
        List<Player> players = new ArrayList<Player>();

        // Find highest rank and level in local group
        int maxRank = AbyssRankEnum.GRADE9_SOLDIER.getId();
        int maxLevel = 0;

        for (Player member : group.getMembers()) {
            if (MathUtil.isIn3dRange(member, victim, GroupConfig.GROUP_MAX_DISTANCE)) {
                // Don't distribute AP to a dead player!
                if (!member.getLifeStats().isAlreadyDead()) {
                    players.add(member);
                    if (member.getLevel() > maxLevel)
                        maxLevel = member.getLevel();
                    if (member.getAbyssRank().getRank().getId() > maxRank)
                        maxRank = member.getAbyssRank().getRank().getId();
                }
            }
        }

        // They are all dead or out of range.
        if (players.isEmpty())
            return false;

        int baseApReward = StatFunctions.calculatePvpApGained(victim, maxRank, maxLevel);
        int baseXpReward = StatFunctions.calculatePvpXpGained(victim, maxRank, maxLevel);
        int baseDpReward = StatFunctions.calculatePvpDpGained(victim, maxRank, maxLevel);
        float groupPercentage = (float) aggro.getDamage() / totalDamage;
        int apRewardPerMember = Math.round(baseApReward * groupPercentage / players.size());
        int xpRewardPerMember = Math.round(baseXpReward * groupPercentage / players.size());
        int dpRewardPerMember = Math.round(baseDpReward * groupPercentage / players.size());

        for (Player member : players) {
            int memberApGain = 1;
            int memberXpGain = 1;
            int memberDpGain = 1;
            if (this.getKillsFor(member.getObjectId(), victim.getObjectId()) < CustomConfig.MAX_DAILY_PVP_KILLS) {
                if (apRewardPerMember > 0)
                    memberApGain = Math.round(RewardType.AP_PLAYER.calcReward(member, apRewardPerMember));
                if (xpRewardPerMember > 0)
                    memberXpGain = Math.round(xpRewardPerMember * member.getRates().getXpPlayerGainRate());
                if (dpRewardPerMember > 0)
                    memberDpGain = Math.round(StatFunctions.adjustPvpDpGained(dpRewardPerMember, victim.getLevel(), member.getLevel()) * member.getRates().getDpPlayerRate());

            }
            AbyssPointsService.addAp(member, victim, memberApGain);
            member.getCommonData().addExp(memberXpGain, RewardType.PVP_KILL, victim.getName());
            member.getCommonData().addDp(memberDpGain);
            this.addKillFor(member.getObjectId(), victim.getObjectId());
        }

        return true;
    }

    /**
     * @param victim
     * @param totalDamage
     * @param aggro
     * @return true if group is not same race
     */
    private boolean rewardPlayerAlliance(Player victim, int totalDamage, AggroInfo aggro) {
        // Reward Alliance
        PlayerAlliance alliance = ((PlayerAlliance) aggro.getAttacker());

        // Don't Reward Player of Same Faction.
        if (alliance.getLeaderObject().getRace() == victim.getRace())
            return false;

        // Find group members in range
        List<Player> players = new ArrayList<Player>();

        // Find highest rank and level in local group
        int maxRank = AbyssRankEnum.GRADE9_SOLDIER.getId();
        int maxLevel = 0;

        for (Player member : alliance.getMembers()) {
            if (!member.isOnline())
                continue;
            if (MathUtil.isIn3dRange(member, victim, GroupConfig.GROUP_MAX_DISTANCE)) {
                // Don't distribute AP to a dead player!
                if (!member.getLifeStats().isAlreadyDead()) {
                    players.add(member);
                    if (member.getLevel() > maxLevel)
                        maxLevel = member.getLevel();
                    if (member.getAbyssRank().getRank().getId() > maxRank)
                        maxRank = member.getAbyssRank().getRank().getId();
                }
            }
        }

        // They are all dead or out of range.
        if (players.isEmpty())
            return false;

        int baseApReward = StatFunctions.calculatePvpApGained(victim, maxRank, maxLevel);
        int baseXpReward = StatFunctions.calculatePvpXpGained(victim, maxRank, maxLevel);
        int baseDpReward = StatFunctions.calculatePvpDpGained(victim, maxRank, maxLevel);
        float groupPercentage = (float) aggro.getDamage() / totalDamage;
        int apRewardPerMember = Math.round(baseApReward * groupPercentage / players.size());
        int xpRewardPerMember = Math.round(baseXpReward * groupPercentage / players.size());
        int dpRewardPerMember = Math.round(baseDpReward * groupPercentage / players.size());

        for (Player member : players) {
            int memberApGain = 1;
            int memberXpGain = 1;
            int memberDpGain = 1;
            if (this.getKillsFor(member.getObjectId(), victim.getObjectId()) < CustomConfig.MAX_DAILY_PVP_KILLS) {
                if (apRewardPerMember > 0)
                    memberApGain = Math.round(RewardType.AP_PLAYER.calcReward(member, apRewardPerMember));
                if (xpRewardPerMember > 0)
                    memberXpGain = Math.round(xpRewardPerMember * member.getRates().getXpPlayerGainRate());
                if (dpRewardPerMember > 0)
                    memberDpGain = Math.round(StatFunctions.adjustPvpDpGained(dpRewardPerMember, victim.getLevel(), member.getLevel()) * member.getRates().getDpPlayerRate());
            }
            AbyssPointsService.addAp(member, victim, memberApGain);
            member.getCommonData().addExp(memberXpGain, RewardType.PVP_KILL, victim.getName());
            member.getCommonData().addDp(memberDpGain);

            this.addKillFor(member.getObjectId(), victim.getObjectId());
        }

        return true;
    }

    /**
     * @param victim
     * @param totalDamage
     * @param aggro
     * @return true if player is not same race
     */
    private boolean rewardPlayer(Player victim, int totalDamage, AggroInfo aggro) {
        // Reward Player
        Player winner = ((Player) aggro.getAttacker());

        // Don't Reward Player out of range/dead/same faction
        /**if (winner.getRace() == victim.getRace() || !MathUtil.isIn3dRange(winner, victim, GroupConfig.GROUP_MAX_DISTANCE) || winner.getLifeStats().isAlreadyDead())
            return false;*/
        if (winner.getRace() == victim.getRace() && (!FFAInstance.isInBG(winner) || !FFAManager.isInFFA(winner)) || !MathUtil.isIn3dRange(winner, victim, GroupConfig.GROUP_MAX_DISTANCE) || winner.getLifeStats().isAlreadyDead()) {
            return false;
        }

        int baseApReward = 1;
        int baseXpReward = 1;
        int baseDpReward = 1;

        if (this.getKillsFor(winner.getObjectId(), victim.getObjectId()) < CustomConfig.MAX_DAILY_PVP_KILLS) {
            baseApReward = StatFunctions.calculatePvpApGained(victim, winner.getAbyssRank().getRank().getId(),
                    winner.getLevel());
            baseXpReward = StatFunctions.calculatePvpXpGained(victim, winner.getAbyssRank().getRank().getId(),
                    winner.getLevel());
            baseDpReward = StatFunctions.calculatePvpDpGained(victim, winner.getAbyssRank().getRank().getId(),
                    winner.getLevel());
        }

        /**int apPlayerReward = Math.round(baseApReward * aggro.getDamage() / totalDamage);
        apPlayerReward = (int) RewardType.AP_PLAYER.calcReward(winner, apPlayerReward);
        int xpPlayerReward = Math.round(baseXpReward * winner.getRates().getXpPlayerGainRate() * aggro.getDamage()
                / totalDamage);
        int dpPlayerReward = Math.round(baseDpReward * winner.getRates().getDpPlayerRate() * aggro.getDamage()
                / totalDamage);*/
        int apPlayerReward = FFAManager.isInFFA(winner)?0:Math.round(baseApReward * aggro.getDamage() / totalDamage);
        apPlayerReward = (int) RewardType.AP_PLAYER.calcReward(winner, apPlayerReward);
        int xpPlayerReward = FFAManager.isInFFA(winner)?0:Math.round(baseXpReward * winner.getRates().getXpPlayerGainRate() * aggro.getDamage()
                / totalDamage);
        int dpPlayerReward = FFAManager.isInFFA(winner)?0:Math.round(baseDpReward * winner.getRates().getDpPlayerRate() * aggro.getDamage()
                / totalDamage);

        AbyssPointsService.addAp(winner, victim, apPlayerReward);
        winner.getCommonData().addExp(xpPlayerReward, RewardType.PVP_KILL, victim.getName());
        winner.getCommonData().addDp(dpPlayerReward);
        this.addKillFor(winner.getObjectId(), victim.getObjectId());
        return true;
    }

    private void notifyKillQuests(Player winner, Player victim) {
        if (winner.getRace() == victim.getRace())
            return;

        List<Player> rewarded = new ArrayList<Player>();
        int worldId = victim.getWorldId();

        if (winner.isInGroup2()) {
            rewarded.addAll(winner.getPlayerGroup2().getOnlineMembers());
        } else if (winner.isInAlliance2()) {
            rewarded.addAll(winner.getPlayerAllianceGroup2().getOnlineMembers());
        } else
            rewarded.add(winner);

        for (Player p : rewarded) {
            if (!MathUtil.isIn3dRange(p, victim, GroupConfig.GROUP_MAX_DISTANCE) || p.getLifeStats().isAlreadyDead())
                continue;

            QuestEngine.getInstance().onKillInWorld(new QuestEnv(victim, p, 0, 0), worldId);
            QuestEngine.getInstance().onKillRanked(new QuestEnv(victim, p, 0, 0), victim.getAbyssRank().getRank());
        }
        rewarded.clear();
    }
    
    public void PVPKillAnnounce(final Player victim) {
  		final Player winner = victim.getAggroList().getMostPlayerDamage();
  		final String loc = MuiService.getInstance().getMessage("LOCATE_STRING01",victim.getWorldId(),victim.getX(),victim.getY());
  		String victimrace = "";
  		String winrace = "";
  		if (victim.getRace() == Race.ASMODIANS) {
  			victimrace = MuiService.getInstance().getMessage("NAMERACE_STRING02");
  			winrace = MuiService.getInstance().getMessage("NAMERACE_STRING01");
  		}
  		else {
  			victimrace = MuiService.getInstance().getMessage("NAMERACE_STRING01");
  			winrace = MuiService.getInstance().getMessage("NAMERACE_STRING02");
  		}
  		final String race1 = winrace, race2 = victimrace;
  		World.getInstance().doOnAllPlayers(new Visitor<Player>() {

  			@Override
  			public void visit(Player p) {
  				if (FFAManager.isInFFA(winner) || FFAInstance.isInBG(winner)){
  					//NO MSG
  				} else {
  				if (MessageCustom.STRING_ANNOUNCEDIED_PVP_ENABLE) {
  				PacketSendUtility.sendPacket(p, new SM_SYSTEM_MESSAGE(MessageCustom.STRING_ANNOUNCEDIED_PVP,victim.getName(),winner.getName(),loc));
  				} else {
  				PacketSendUtility.sendMessage(p, "\uE022[JCJ]: " + MuiService.getInstance().getMessage("PVPSERVICE_MSG01",winner.getName(),race1,victim.getName(),race2,loc));
  				}
  				}
  			}
  		});
  	}
    
    private void sendPacketMESSAGE(final AionServerPacket packet) {
		World.getInstance().doOnAllPlayers(new Visitor<Player>() {

			@Override
			public void visit(Player findedPlayer) {
				PacketSendUtility.sendPacket(findedPlayer, packet);
			}

		});
	}

    @SuppressWarnings("synthetic-access")
    private static class SingletonHolder {

        protected static final PvpService instance = new PvpService();
    }
}